#include "proc.h"

static struct proc_dir_entry *adpm_proc_dir = NULL;

static int adpm_proc_file_show(struct seq_file *f, void *v)
{
  struct adpm_client_file *cf;
  unsigned long bkt, flags;
  struct rb_node *p;
  struct adpm_page *pg;
  struct adpm_dirty *di;
  long total;
  
  seq_printf(f, "FILE INFO\n");
  spin_lock(&adpm_ftab_lock);
  hash_for_each(adpm_ftab, bkt, cf, hash_head){
    seq_printf(f, "   file:           %s\n", cf->name);
    seq_printf(f, "     id            %ld\n", cf->base_info.id);
    seq_printf(f, "     size          %ld\n", cf->base_info.size);
    seq_printf(f, "     version       %ld\n", cf->base_info.version);
    seq_printf(f, "     open cnt:     %d\n", atomic_read(&cf->open_cnt));
    seq_printf(f, "     mmap cnt      %d\n", atomic_read(&cf->mmap_cnt));
    seq_printf(f, "     page info(pgoff-lru-mapped-dirty):\n");
    seq_printf(f, "       ");
    total = 0;
    spin_lock_irqsave(&cf->pages_lock, flags);
    p = rb_first(&cf->pages);
    while(p){
      ++total;
      pg = container_of(p, struct adpm_page, vm_node);
      spin_lock(&pg->lru_lock);
      seq_printf(f, "%ld-%ld-%d-%ld\t\t", pg->vm_pgoff, adpm_page_get_stat(pg), !!pg->rmap.pt, pg->dirty_cnt);
      spin_unlock(&pg->lru_lock);
      p = rb_next(p);
    }
    spin_unlock_irqrestore(&cf->pages_lock, flags);
    seq_printf(f, "\n     total pages %ld\n", total);

    seq_printf(f, "     dirty info: \n");
    spin_lock_irqsave(&cf->dirtys_lock, flags);
    seq_printf(f, "       ");
    p = rb_first(&cf->dirtys);
    while(p){
      di = container_of(p, struct adpm_dirty, node);
      seq_printf(f, "%ld-%ld   ", di->start_vir - cf->vminfo.vm_start, di->end_vir - cf->vminfo.vm_start);
      p = rb_next(p);
    }
    spin_unlock_irqrestore(&cf->dirtys_lock, flags);
  }
  spin_unlock(&adpm_ftab_lock);
  return 0;
}
static int adpm_proc_info_show(struct seq_file *f, void *v)
{
  struct adpm_sb_info *sbi;
  unsigned long flags;
  struct adpm_client_file *cf;
  long bkt;

  sbi = adpm_buddy->sbi;
  seq_printf(f, "ADPM INFO\n");
  seq_printf(f, "super block info:\n");
  seq_printf(f, "   phys addr    %lx\n", sbi->phys_addr);
  seq_printf(f, "   virt addr    %lx\n", sbi->virt_addr);
  seq_printf(f, "   size         %lu\n", sbi->size);
  seq_printf(f, "   nr page      %lu\n", sbi->nr_pages);
  seq_printf(f, "buddy info\n");
  seq_printf(f, "   start pfn    %lu\n", adpm_buddy->start_pfn);
  
  spin_lock_irqsave(&adpm_buddy->free_lock, flags);
  seq_printf(f, "   free page    %lu\n", adpm_buddy->nr_free);
  spin_unlock_irqrestore(&adpm_buddy->free_lock, flags);
  
  spin_lock_irqsave(&adpm_buddy->lru_list_lock, flags);
  seq_printf(f, "   inact page   %lu\n", adpm_buddy->nr_inact_list);
  seq_printf(f, "   activ page   %lu\n", adpm_buddy->nr_act_list);
  spin_unlock_irqrestore(&adpm_buddy->lru_list_lock, flags);
  seq_printf(f, "   dirty page   %lu\n", adpm_buddy->nr_dirty_list);
  seq_printf(f, "file info\n");
    
  spin_lock(&adpm_ftab_lock);
  hash_for_each(adpm_ftab, bkt, cf, hash_head){
    seq_printf(f, "   file:           %s\n", cf->name);
    seq_printf(f, "     id            %ld\n", cf->base_info.id);
    seq_printf(f, "     size          %ld\n", cf->base_info.size);
    seq_printf(f, "     version       %ld\n", cf->base_info.version);
    seq_printf(f, "     open cnt:     %d\n", atomic_read(&cf->open_cnt));
    seq_printf(f, "     mmap cnt      %d\n", atomic_read(&cf->mmap_cnt));
  }
  spin_unlock(&adpm_ftab_lock);
  return 0;
}

enum{
  Opt_del_file, //delete a file
  Opt_err,
};
static const match_table_t adpm_info_token = {
    { Opt_del_file, "del=%s" },
    { Opt_err, NULL },
};
static ssize_t adpm_proc_info_write(struct file *file, const char __user *buf, 
                size_t count, loff_t *offs)
{
  int option;
  char *p, *options, *orig;
  substring_t args[MAX_OPT_ARGS];
  if(count >= ADPM_STR_LEN)
    return -EINVAL;
  orig = options = kzalloc(count, GFP_KERNEL);
  if(!options)
    return -ENOMEM;
  if(copy_from_user(options, buf, count)){
    kfree(orig);
    return -EFAULT;
  }
  if(options[count - 1] == '\n')
    options[count - 1] = '\0';
  while((p = strsep(&options, ",")) != NULL){
    int token;
    if(!*p)
      continue;
    token = match_token(p, adpm_info_token, args);
    switch(token){
      case Opt_del_file:
        *args[0].to = '\0';
	adpm_pr("del file %s\n", args[0].from);
        adpm_del_file(args[0].from);
        break;
      default:
        pr_err("ERROR: %s adpm proc info wrong\n", __FUNCTION__);
        break;
    }
  }
  kfree(orig);
  return count;
}

static int adpm_proc_info_open(struct inode *inode, struct file *file)
{
  return single_open(file, adpm_proc_info_show, NULL);
}

static int adpm_proc_file_open(struct inode *inode, struct file *file)
{
  return single_open(file, adpm_proc_file_show, NULL);
}

static const struct file_operations adpm_proc_info_ops = {
  .open   = adpm_proc_info_open,
  .read   = seq_read,
  .write    = adpm_proc_info_write,
  .llseek   = seq_lseek,
  .release  = single_release
};
  
static const struct file_operations adpm_proc_file_ops = {
  .open   = adpm_proc_file_open,
  .read   = seq_read,
  .write    = NULL,
  .llseek   = seq_lseek,
  .release  = single_release
};


int adpm_create_proc(void)
{
  adpm_proc_dir = proc_mkdir("adpm", NULL);
  if(!adpm_proc_dir){
    pr_err("ERROR: %s adpm_proc_dir create failed\n", __FUNCTION__);
    return -ENOMEM;
  }
  proc_create("info", S_IRUGO | S_IWUGO, adpm_proc_dir, &adpm_proc_info_ops);
  proc_create("file", S_IRUGO, adpm_proc_dir, &adpm_proc_file_ops);
  return 0;
}
void adpm_remove_proc(void)
{
  if(!adpm_proc_dir)return ;
  remove_proc_entry("file", adpm_proc_dir);
  remove_proc_entry("info", adpm_proc_dir);
  proc_remove(adpm_proc_dir);
  adpm_proc_dir = NULL;
}
